/*
 * Copyright (C) 2017 Fromentin Xavier
 *
 * This file is part of GDOB.
 *
 * GDOB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GDOB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.grimdawnofflinebuilder.util.template.templateReader

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.grimdawnofflinebuilder.util.config.GDOBConfig
import com.grimdawnofflinebuilder.util.config.Preferences
import com.grimdawnofflinebuilder.util.config.RecordsReaderConfig
import com.grimdawnofflinebuilder.util.template.classGeneration.ClassGenerator
import com.grimdawnofflinebuilder.util.template.templateReader.parser.TPLLexer
import com.grimdawnofflinebuilder.util.template.templateReader.parser.TPLParser
import org.antlr.v4.runtime.ANTLRInputStream
import org.antlr.v4.runtime.CommonTokenStream
import java.io.File

/**
 * Template's variable class
 *
 * @property name variable's name
 * @property clazz variable's class
 * @property type variable's type
 * @property description variable's description
 * @property value variable's value
 * @property defaultValue variable's default value
 */
data class Variable( var name: String = "",
                     var clazz: String = "",
                     var type: String = "",
                     var description: String = "",
                     var value: String = "",
                     var defaultValue: String = "" )

/**
 * Template's Group class
 *
 * @property templateFile file of the template containing the group if the group is the root group
 * @property name group's name
 * @property type group's type
 * @property variables variables of the root group
 * @property subGroups groups contained in the root group
 */
data class Group( var templateFile: File? = null,
                  var name: String = "",
                  var type: String = "",
                  val variables: MutableList<Variable> = mutableListOf(),
                  val subGroups: MutableList<Group> = mutableListOf() )

/**
 * Read the template file and extract the Group and variables from it
 */
class TemplatesReader {

    /**
     * Mapping of "included file" variables and their corresponding groups
     */
    private var varMap = mutableMapOf<Variable, Group>()

    /**
     * Companion objects
     */
    companion object {
        private val preferences = GDOBConfig.preferences
        private val readerConfig = GDOBConfig.recordsReaderConfig
    }

    /**
     * Read the needed templates files
     *
     * @return list of groups extracted from the templates files coupled with the mapping of included variables
     *         and the corresponding groups
     */
    fun readTemplates() : Pair<List<Group>, Map<Variable, Group>> {
        varMap = mutableMapOf<Variable, Group>()
        println( "Reading direct templates...")
        val groups = lookUpTemplateFileToRead()
          .map { readTemplate( it ) }

        return Pair( groups, varMap )
    }

    /**
     * Read dbr files in targeted folder to determine which template files are needed
     *
     * @return list of template names read in dbr files
     */
    fun lookUpTemplateFileToRead() : List<File> {
        val foldersNames = readerConfig.itemsFilesFolders.gear

        // Get all dbr files
        val dbrFiles = mutableListOf<File>()
        foldersNames.map { it.toRecordsFile( preferences, readerConfig ) }
          .map { it.walk() }
          .flatMap { it.toList() }
          .filter { it.isFile }
          .map {
              println( "Reading file ${it.name}..." )
              val br = it.bufferedReader()
              val firstLine = br.readLine()
              val tab = firstLine.split( "," )
              if( tab[0] == "templateName" ) {
                  tab[1]
              } else {
                  throw Exception( "First line of file is not the <templateName> parameters in file ${it.name}" )
              }
          }
          .distinct()
          .map { File( "${preferences.installDir}\\$it" ) }
          .forEach { dbrFiles.add( it ) }

        return dbrFiles
    }

    /**
     * Convert the json in the given file to objects
     *
     * @param templateFile the template file to read and convert
     * @return The root group of the template file
     */
    private fun readTemplate( templateFile: File ) : Group {
        // Read main template from file in parameter
        println( "Reading template ${templateFile.name}...")

        val fis = templateFile.inputStream()
        val antlrStream = ANTLRInputStream( fis )
        val lexer = TPLLexer( antlrStream )
        val tokenStream = CommonTokenStream( lexer )
        val parser = TPLParser( tokenStream )
        val parseTree = parser.group()

        val tplVisitor = TPLVisitorImpl()
        val mainGroup = tplVisitor.visit( parseTree )
        mainGroup.templateFile = templateFile

        // Recursively read included templates
        println( "Resolving included templates..." )
        mainGroup.variables
          .filter { it.type == "include" }
          .distinct()
          .associateBy( { it }, { readTemplate( File( "${preferences.installDir}\\${it.defaultValue}" ) ) } )
          .forEach { entry ->
              varMap.put( entry.key, entry.value )
          }

        return mainGroup
    }

}

/**
 * Constructs the file path to the record folder whose name is given in parameters
 *
 * @param pref preferences
 * @param rc configuration for thr records reader
 *
 * @return the file object corresponding to the constructed file path
 */
fun String.toRecordsFile( pref: Preferences, rc: RecordsReaderConfig ) : File {
    val filePath = mutableListOf(pref.installDir, rc.recordsDir, rc.itemDir, this )
      .joinToString("\\")
    return File( filePath )
}

/**
 * Main func
 */
fun main( args : Array<String> ) {
    val mapper = jacksonObjectMapper()

    // Read templates
    val templateReader = TemplatesReader()
    val ( groups, varMap ) = templateReader.readTemplates()
    groups.forEach { println( mapper.writeValueAsString( it ) ) }

    /*val varTypes = mutableSetOf<String>()
    groups.forEach { varTypes.addAll( it.variables.map { it.type } ) }
    varTypes.addAll(
      groups.flatMap { it.subGroups }
        .flatMap { it.variables }
        .map { it.type }
    )

    println( varTypes )*/

    // Generate classes from direct templates
    val classGenerator = ClassGenerator()
    groups.forEach { classGenerator.generateClass( it, varMap, false ) }

    // Generate classes from included templates
    varMap.entries
      .filter { it.key.type == "include" }
      .forEach { classGenerator.generateClass( it.value, varMap, true  ) }
}